﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Beacon.SPC
{
    public enum ResultType
    { 
        Success,
        Warning,
        Error
    }

    public class SpcTrainingResult
    {
        public static string LessInControl = "Too less stable inputting data (in-control data)";
        public static string AbnormalInput = "Abnormal input data";
        public static string Success = "Training successfully";

        public ResultType Type { get; set; } 
        public string Message { get; set; }

        public double XbarMean { get; set; }
        public double RangeMean { get; set; }
        public double StdMean { get; set; }
        public double Sigma { get; set; }
        public List<SpcData> ViolatedPoints = new List<SpcData>();

        public void Clear()
        {
            Type = ResultType.Success;
            Message = Success;

            XbarMean = double.NaN;
            RangeMean = double.NaN;
            StdMean = double.NaN;
            Sigma = double.NaN;

            ViolatedPoints.Clear();
        }

        public override string ToString()
        {
            string rlt = Type.ToString() + " with message : " + Message + Environment.NewLine
                         + "XbarMean is " + string.Format("{0:0.####}",XbarMean) + Environment.NewLine
                         + "Range Mean is " + string.Format("{0:0.####}",RangeMean) + Environment.NewLine
                         + "StdMean is " + string.Format("{0:0.####}",StdMean) + Environment.NewLine
                         + "Sigma is " + string.Format("{0:0.####}",Sigma) + Environment.NewLine;

            if (ViolatedPoints.Count > 0)
            {
                rlt += "violated points :" + Environment.NewLine;
                foreach (SpcData d in ViolatedPoints)
                {
                    rlt += d.ToString();
                }
            }

            return rlt;
        }
    }


    public class SpcTraining
    {
        #region Member 
        
        private const double minAcceptRatio = 0.8;
        private const int minDataCount = 20;
        private bool _fixedSubgroup = true;
        private SpcTrainingResult _result = new SpcTrainingResult();
        
        #endregion

        #region Public functions

        public SpcTrainingResult Train(List<SpcData> dataList, 
                                        List<SpcRuleDefinition> rules, 
                                        SpcChartType type, 
                                        ScalarInspection insp, 
                                        bool fixedSubgroupSize)
        {
            _result.Clear();
            _fixedSubgroup = fixedSubgroupSize;
            List<SpcData> data = dataList.ToList();

            switch (type)
            {
                case SpcChartType.XBarR:
                    return TrainXBarR(data, rules);
                case SpcChartType.XBarS:
                    return TrainXbarS(data, rules);
                case SpcChartType.XMR:
                    return TrainMR(data, rules);
                case SpcChartType.C:
                    return TrainC(data, rules, insp);
                case SpcChartType.U:
                    return TrainU(data, rules, insp);
                case SpcChartType.NP:
                    return TrainNP(data, rules, insp);
                case SpcChartType.P:
                    return TrainP(data, rules, insp);
                default:
                    throw new SpcException(string.Format("spc data type {0} not support", type.ToString()));
            }
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Trainging xbar-range chart
        /// </summary>
        /// <param name="data"></param>
        /// <param name="rules"></param>
        /// <returns></returns>
        SpcTrainingResult TrainXBarR(List<SpcData> data, List<SpcRuleDefinition> rules)
        {
            if (data.Count <= 0 || rules.Count <= 0)
                return null;

            int count = data.Count;
            bool finished = false;
            
            while (!finished && (double)data.Count/count > minAcceptRatio)
            {
                finished = true;

                SpcControlLimit XbarLimit = new SpcControlLimit(SpcChartType.XBarR, SpcDataType.Mean, data, data[0].Count(), null);
                SpcControlLimit RangeLimit = new SpcControlLimit(SpcChartType.XBarR, SpcDataType.Range, data, data[0].Count(), null);

                List<ISpcRule> ruleXbarList = new List<ISpcRule>();
                List<ISpcRule> ruleRangeList = new List<ISpcRule>();

                foreach (SpcRuleDefinition define in rules)
                {
                    ruleXbarList.Add(SpcRuleFactory.CreateRule(XbarLimit, define));
                    ruleRangeList.Add(SpcRuleFactory.CreateRule(RangeLimit, define));
                }

                foreach (SpcData d in data)
                {
                    foreach (ISpcRule r in ruleXbarList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.XBar) : r.AddPoint(d.XBar, d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }

                    foreach (ISpcRule r in ruleRangeList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.Range) : r.AddPoint(d.Range, d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }
                }

                data.RemoveAll(d => d.Violated == true);
            }

            if (!finished)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.AbnormalInput;
            }

            if (data.Count <= minDataCount)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.LessInControl;
            }

            SpcControlLimit XbarResult = new SpcControlLimit(SpcChartType.XBarR, SpcDataType.Mean, data, data[0].Count(), null);
            SpcControlLimit RangeResult = new SpcControlLimit(SpcChartType.XBarR, SpcDataType.Range, data, data[0].Count(), null);

            _result.XbarMean = XbarResult.Mean;
            _result.RangeMean = RangeResult.Mean;
            _result.Sigma = XbarResult.Sigma;

            return _result;
        }

        /// <summary>
        /// Trainging xbar-stdev chart
        /// </summary>
        /// <param name="data"></param>
        /// <param name="rules"></param>
        /// <returns></returns>
        SpcTrainingResult TrainXbarS(List<SpcData> data, List<SpcRuleDefinition> rules)
        {
            if (data.Count <= 0 || rules.Count <= 0)
                return null;

            int count = data.Count;
            bool finished = false;

            while (!finished && (double)data.Count / count > minAcceptRatio)
            {
                finished = true;

                SpcControlLimit XbarLimit = new SpcControlLimit(SpcChartType.XBarS, SpcDataType.Mean, data, data[0].Count(), null);
                SpcControlLimit StdevLimit = new SpcControlLimit(SpcChartType.XBarS, SpcDataType.Std, data, data[0].Count(), null);

                List<ISpcRule> ruleXbarList = new List<ISpcRule>();
                List<ISpcRule> ruleStdevList = new List<ISpcRule>();

                foreach (SpcRuleDefinition define in rules)
                {
                    ruleXbarList.Add(SpcRuleFactory.CreateRule(XbarLimit, define));
                    ruleStdevList.Add(SpcRuleFactory.CreateRule(StdevLimit, define));
                }

                foreach (SpcData d in data)
                {
                    foreach (ISpcRule r in ruleXbarList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.XBar) : r.AddPoint(d.XBar, d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }

                    foreach (ISpcRule r in ruleStdevList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.StDev) : r.AddPoint(d.StDev, d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }
                }

                data.RemoveAll(d => d.Violated == true);
            }

            if (!finished)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.AbnormalInput;
            }

            if (data.Count <= minDataCount)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.LessInControl;
            }

            SpcControlLimit XbarResult = new SpcControlLimit(SpcChartType.XBarS, SpcDataType.Mean, data, data[0].Count(), null);
            SpcControlLimit StdevResult = new SpcControlLimit(SpcChartType.XBarS, SpcDataType.Std, data, data[0].Count(), null);

            _result.XbarMean = XbarResult.Mean;
            _result.StdMean = StdevResult.Mean;
            _result.Sigma = XbarResult.Sigma;

            return _result;
        }

        /// <summary>
        /// Trainging xbar-moving range chart
        /// </summary>
        /// <param name="data"></param>
        /// <param name="rules"></param>
        /// <returns></returns>
        SpcTrainingResult TrainMR(List<SpcData> data, List<SpcRuleDefinition> rules)
        {
            if (data.Count <= 0 || rules.Count <= 0)
                return null;

            int count = data.Count;
            bool finished = false;

            while (!finished && (double)data.Count / count > minAcceptRatio)
            {
                finished = true;

                SpcControlLimit XbarLimit = new SpcControlLimit(SpcChartType.XMR, SpcDataType.Mean, data, data[0].Count(), null);
                SpcControlLimit RangeLimit = new SpcControlLimit(SpcChartType.XMR, SpcDataType.Range, data, data[0].Count(), null);

                List<ISpcRule> ruleXbarList = new List<ISpcRule>();
                List<ISpcRule> ruleRangeList = new List<ISpcRule>();

                foreach (SpcRuleDefinition define in rules)
                {
                    ruleXbarList.Add(SpcRuleFactory.CreateRule(XbarLimit, define));
                    ruleRangeList.Add(SpcRuleFactory.CreateRule(RangeLimit, define));
                }

                foreach (SpcData d in data)
                {
                    foreach (ISpcRule r in ruleXbarList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.X0) : r.AddPoint(d.X0, d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }

                    foreach (ISpcRule r in ruleRangeList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.MR) : r.AddPoint(d.MR, d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }
                }

                data.RemoveAll(d => d.Violated == true);
            }

            if (!finished)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.AbnormalInput;
            }

            if (data.Count <= minDataCount)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.LessInControl;
            }

            SpcControlLimit XbarResult = new SpcControlLimit(SpcChartType.XMR, SpcDataType.Mean, data, data[0].Count(), null);
            SpcControlLimit RangeResult = new SpcControlLimit(SpcChartType.XMR, SpcDataType.Range, data, data[0].Count(), null);            

            _result.XbarMean = XbarResult.Mean;
            _result.RangeMean = RangeResult.Mean;
            _result.Sigma = XbarResult.Sigma;

            return _result;
        }

        /// <summary>
        /// Training C chart  
        /// </summary>
        /// <param name="data"></param>
        /// <param name="rules"></param>
        /// <param name="incp"></param>
        /// <returns></returns>
        SpcTrainingResult TrainC(List<SpcData> data, List<SpcRuleDefinition> rules, ScalarInspection insp)
        {
            if (data.Count <= 0 || rules.Count <= 0)
                return null;

            int count = data.Count;
            bool finished = false;

            while (!finished && (double)data.Count / count > minAcceptRatio)
            {
                finished = true;

                SpcControlLimit XbarLimit = new SpcControlLimit(SpcChartType.C, SpcDataType.Mean, data, data[0].Count(), insp);                

                List<ISpcRule> ruleXbarList = new List<ISpcRule>();
                
                foreach (SpcRuleDefinition define in rules)
                {
                    ruleXbarList.Add(SpcRuleFactory.CreateRule(XbarLimit, define));
                }

                foreach (SpcData d in data)
                {
                    foreach (ISpcRule r in ruleXbarList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.C(insp)) : r.AddPoint(d.C(insp), d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }
                }
                data.RemoveAll(d => d.Violated == true);
            }

            if (!finished)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.AbnormalInput;
            }

            if (data.Count <= minDataCount)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.LessInControl;
            }

            SpcControlLimit XbarResult = new SpcControlLimit(SpcChartType.C, SpcDataType.Mean, data, data[0].Count(), insp);

            _result.XbarMean = XbarResult.Mean;
            _result.Sigma = XbarResult.Sigma;

            return _result;
        }

        /// <summary>
        /// Training U chart
        /// </summary>
        /// <param name="data"></param>
        /// <param name="rules"></param>
        /// <param name="insp"></param>
        /// <returns></returns>
        SpcTrainingResult TrainU(List<SpcData> data, List<SpcRuleDefinition> rules, ScalarInspection insp)
        {
            if (data.Count <= 0 || rules.Count <= 0)
                return null;

            int count = data.Count;
            bool finished = false;

            while (!finished && (double)data.Count / count > minAcceptRatio)
            {
                finished = true;

                SpcControlLimit XbarLimit = new SpcControlLimit(SpcChartType.U, SpcDataType.Mean, data, data[0].Count(), insp);

                List<ISpcRule> ruleXbarList = new List<ISpcRule>();

                foreach (SpcRuleDefinition define in rules)
                {
                    ruleXbarList.Add(SpcRuleFactory.CreateRule(XbarLimit, define));
                }

                foreach (SpcData d in data)
                {
                    foreach (ISpcRule r in ruleXbarList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.U(insp)) : r.AddPoint(d.U(insp), d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }
                }
                data.RemoveAll(d => d.Violated == true);
            }

            if (!finished)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.AbnormalInput;
            }

            if (data.Count <= minDataCount)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.LessInControl;
            }

            SpcControlLimit XbarResult = new SpcControlLimit(SpcChartType.U, SpcDataType.Mean, data, data[0].Count(), insp);

            _result.XbarMean = XbarResult.Mean;
            _result.Sigma = XbarResult.Sigma;

            return _result;
        }

        /// <summary>
        /// Training NP chart
        /// </summary>
        /// <param name="data"></param>
        /// <param name="rules"></param>
        /// <param name="insp"></param>
        /// <returns></returns>
        SpcTrainingResult TrainNP(List<SpcData> data, List<SpcRuleDefinition> rules, ScalarInspection insp)
        {
            if (data.Count <= 0 || rules.Count <= 0)
                return null;

            int count = data.Count;
            bool finished = false;

            while (!finished && (double)data.Count / count > minAcceptRatio)
            {
                finished = true;

                SpcControlLimit XbarLimit = new SpcControlLimit(SpcChartType.NP, SpcDataType.Mean, data, data[0].Count(), insp);

                List<ISpcRule> ruleXbarList = new List<ISpcRule>();

                foreach (SpcRuleDefinition define in rules)
                {
                    ruleXbarList.Add(SpcRuleFactory.CreateRule(XbarLimit, define));
                }

                foreach (SpcData d in data)
                {
                    foreach (ISpcRule r in ruleXbarList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.NP(insp)) : r.AddPoint(d.NP(insp), d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }
                }
                data.RemoveAll(d => d.Violated == true);
            }

            if (!finished)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.AbnormalInput;
            }

            if (data.Count <= minDataCount)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.LessInControl;
            }

            SpcControlLimit XbarResult = new SpcControlLimit(SpcChartType.NP, SpcDataType.Mean, data, data[0].Count(), insp);

            _result.XbarMean = XbarResult.Mean;
            _result.Sigma = XbarResult.Sigma;

            return _result;
        }

        /// <summary>
        /// Training P chart
        /// </summary>
        /// <param name="data"></param>
        /// <param name="rules"></param>
        /// <param name="insp"></param>
        /// <returns></returns>
        SpcTrainingResult TrainP(List<SpcData> data, List<SpcRuleDefinition> rules, ScalarInspection insp)
        {
            if (data.Count <= 0 || rules.Count <= 0)
                return null;

            int count = data.Count;
            bool finished = false;

            while (!finished && (double)data.Count / count > minAcceptRatio)
            {
                finished = true;

                SpcControlLimit XbarLimit = new SpcControlLimit(SpcChartType.P, SpcDataType.Mean, data, data[0].Count(), insp);

                List<ISpcRule> ruleXbarList = new List<ISpcRule>();

                foreach (SpcRuleDefinition define in rules)
                {
                    ruleXbarList.Add(SpcRuleFactory.CreateRule(XbarLimit, define));
                }

                foreach (SpcData d in data)
                {
                    foreach (ISpcRule r in ruleXbarList)
                    {
                        bool violate = _fixedSubgroup ? r.AddPoint(d.P(insp)) : r.AddPoint(d.P(insp), d.Count());
                        if (violate)
                        {
                            d.Violated = true;
                            finished = false;
                            _result.ViolatedPoints.Add(d);
                        }
                    }
                }
                data.RemoveAll(d => d.Violated == true);
            }

            if (!finished)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.AbnormalInput;
            }

            if (data.Count <= minDataCount)
            {
                _result.Type = ResultType.Warning;
                _result.Message = SpcTrainingResult.LessInControl;
            }

            SpcControlLimit XbarResult = new SpcControlLimit(SpcChartType.P, SpcDataType.Mean, data, data[0].Count(), insp);

            _result.XbarMean = XbarResult.Mean;
            _result.Sigma = XbarResult.Sigma;

            return _result;
        }
        #endregion

    }
}
